001 /*
002 * Copyright 2004-2005 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.transit.tools;
020
021 import java.io.InputStream;
022 import java.util.Vector;
023 import java.util.Hashtable;
024 import java.net.URI;
025
026 import net.dpml.lang.Part;
027 import net.dpml.lang.Plugin;
028 import net.dpml.lang.Resource;
029
030 import net.dpml.util.ElementHelper;
031
032 import org.apache.tools.ant.Project;
033 import org.apache.tools.ant.BuildException;
034 import org.apache.tools.ant.ComponentHelper;
035 import org.apache.tools.ant.SubBuildListener;
036 import org.apache.tools.ant.BuildEvent;
037
038 import org.w3c.dom.Element;
039
040 /**
041 * A component helper that handles automatic loading of plugins into the
042 * ant plugin based on namespace declarations in the project file. This is similar
043 * to the 'antlib:' convention except we use the 'plugin:' convention.
044 *
045 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
046 * @version 1.0.2
047 */
048 public class TransitComponentHelper extends ComponentHelper
049 implements SubBuildListener
050 {
051 //------------------------------------------------------------------------
052 // static
053 //------------------------------------------------------------------------
054
055 /**
056 * The constant Transit ANTLIB namespace.
057 */
058 public static final String TRANSIT_ANTLIB_URN = "antlib:net.dpml.transit";
059
060 /**
061 * The constant Transit ANTLIB init task namespace.
062 */
063 public static final String TRANSIT_INIT_URN = TRANSIT_ANTLIB_URN + ":init";
064
065 /**
066 * The constant Transit ANTLIB plugin task namespace.
067 */
068 public static final String TRANSIT_PLUGIN_URN = TRANSIT_ANTLIB_URN + ":plugin";
069
070 /**
071 * The constant Transit ANTLIB import task namespace.
072 */
073 public static final String TRANSIT_IMPORT_URN = TRANSIT_ANTLIB_URN + ":import";
074
075 /**
076 * The constant Transit ANTLIB get task namespace.
077 */
078 public static final String TRANSIT_GET_URN = TRANSIT_ANTLIB_URN + ":get";
079
080 /**
081 * The constant artifact plugin header.
082 */
083 public static final String PLUGIN_ARTIFACT_HEADER = "artifact:part:";
084
085 /**
086 * Creation of a component helper for the supplied project.
087 *
088 * @param project the project
089 */
090 public static void initialize( Project project )
091 {
092 initialize( project, false );
093 }
094
095 /**
096 * Creation of a component helper for the supplied project.
097 *
098 * @param project the project
099 * @param flag subproject flag
100 */
101 public static void initialize( Project project, boolean flag )
102 {
103 ComponentHelper current =
104 (ComponentHelper) project.getReference( "ant.ComponentHelper" );
105 if( ( null != current ) && ( current instanceof TransitComponentHelper ) )
106 {
107 return;
108 }
109 TransitComponentHelper helper = new TransitComponentHelper( project, current );
110 helper.initDefaultDefinitions();
111 if( flag )
112 {
113 project.log(
114 "\nAssigning Transit component helper to sub-project: "
115 + project.getBaseDir() );
116 }
117 else
118 {
119 project.log(
120 "\nAssigning Transit component helper to project: "
121 + project.getBaseDir() );
122 }
123 project.addReference( "ant.ComponentHelper", helper );
124 project.addBuildListener( helper );
125 }
126
127 /**
128 * Vector of plugin uris already loaded.
129 */
130 private static Vector m_URIS = new Vector();
131
132 /**
133 * Table of urn to uri mappings.
134 */
135 private static Hashtable m_MAPPINGS = new Hashtable();
136
137 /**
138 * Register the mapping between a urn and a plugin uri.
139 * @param maps a sequence of urn to uri bindings
140 */
141 public static void register( MapDataType[] maps )
142 {
143 if( null == maps )
144 {
145 throw new NullPointerException( "maps" );
146 }
147
148 for( int i=0; i < maps.length; i++ )
149 {
150 MapDataType map = maps[i];
151 String urn = map.getURN();
152 if( !m_MAPPINGS.contains( urn ) )
153 {
154 URI uri = map.getURI();
155 m_MAPPINGS.put( urn, uri );
156 }
157 }
158 }
159
160 //------------------------------------------------------------------------
161 // state
162 //------------------------------------------------------------------------
163
164 /**
165 * The current project.
166 */
167 private Project m_project;
168
169 /**
170 * The parent component helper.
171 */
172 private ComponentHelper m_parent;
173
174 //------------------------------------------------------------------------
175 // constructor
176 //------------------------------------------------------------------------
177
178 /**
179 * Creation of a new transit component helper.
180 * @param project the current project
181 */
182 public TransitComponentHelper( Project project )
183 {
184 this( project, ComponentHelper.getComponentHelper( project ) );
185 }
186
187 /**
188 * Creation of a new transit component helper.
189 * @param project the current project
190 * @param parent the parent component helper
191 */
192 public TransitComponentHelper( Project project, ComponentHelper parent )
193 {
194 setProject( project );
195 m_parent = parent;
196 if( null != parent )
197 {
198 parent.setNext( this );
199 }
200
201 Hashtable map = getTaskDefinitions();
202 if( null == map.get( TRANSIT_INIT_URN ) )
203 {
204 addTaskDefinition( TRANSIT_INIT_URN, MainTask.class );
205 addTaskDefinition( TRANSIT_PLUGIN_URN, PluginTask.class );
206 addTaskDefinition( TRANSIT_IMPORT_URN, ImportArtifactTask.class );
207 addTaskDefinition( TRANSIT_GET_URN, GetTask.class );
208 }
209 }
210
211 //------------------------------------------------------------------------
212 // implementation
213 //------------------------------------------------------------------------
214
215 /**
216 * Set the current project.
217 * @param project the current ant project
218 */
219 public void setProject( Project project )
220 {
221 m_project = project;
222 super.setProject( project );
223 }
224
225 /**
226 * Create an object for a component using a supplied name. The name
227 * is the fully qualified component name which allows us to intercept
228 * specific namespace qualifiers - in this case 'plugin:'. In the event of
229 * a plugin namespace we check to see if the plugin for that name is already
230 * loaded and it not we proceed with classic transit-based loading of the
231 * plugin and registration of plugin classes with the component helper.
232 *
233 * @param name the name of the component, if the component is in a namespace, the
234 * name is prefixed with the namespace uri and ":"
235 * @return the class if found or null if not.
236 */
237 public Object createComponent( String name )
238 {
239 Object object = super.createComponent( name );
240 if( null != object )
241 {
242 return object;
243 }
244
245 if( null != m_parent )
246 {
247 object = m_parent.createComponent( name );
248 if( null != object )
249 {
250 return object;
251 }
252 }
253
254 //
255 // from here we need to validate - code has been worked over more than a
256 // few time ans I would not recommend it for flight control scenarios
257 // just yet
258 //
259
260 int k = name.lastIndexOf( ":" );
261 if( k > 0 )
262 {
263 String urn = name.substring( 0, k );
264 String task = name.substring( k + 1 );
265 URI uri = convertUrnToURI( urn );
266 if( null != uri )
267 {
268 installPlugin( uri, urn, task );
269 object = super.createComponent( name );
270 if( null != object )
271 {
272 return object;
273 }
274 else
275 {
276 final String error =
277 "Mapped urn returned a null object.";
278 throw new BuildException( error );
279 }
280 }
281 else
282 {
283 return super.createComponent( name );
284 }
285 }
286 else
287 {
288 return super.createComponent( name );
289 }
290 }
291
292 /**
293 * Convert a urn to a uri taking into account possible urn alias names.
294 *
295 * @param urn the urn to convert to a uri
296 * @return the converted uri
297 */
298 private URI convertUrnToURI( String urn )
299 {
300 URI uri = (URI) m_MAPPINGS.get( urn );
301 if( null != uri )
302 {
303 return uri;
304 }
305 if( urn.startsWith( PLUGIN_ARTIFACT_HEADER ) )
306 {
307 return convertToURI( urn );
308 }
309 else
310 {
311 return null;
312 }
313 }
314
315 /**
316 * The implementation will retrieve the plugin descriptor. If the descriptor
317 * declares a classname then the class will be loaded and assigned under
318 * name. If the classname is undefined and resource is defined, the
319 * implementation will attempt to locate an antlib definition at the resource
320 * location and will attempt to load all taskdef and typedef entries declared
321 * in the antlib.
322 *
323 * @param uri the plugin uri
324 * @param name the fully qualified component name
325 */
326 private void installPlugin( URI uri, String urn, String name )
327 {
328 final String label = uri + ":" + name;
329 if( null != getTaskDefinitions().get( label ) )
330 {
331 return;
332 }
333
334 try
335 {
336 m_project.log( "installing: " + uri + " as " + urn );
337
338 Part part = Part.load( uri );
339
340 ClassLoader current = Thread.currentThread().getContextClassLoader();
341
342 if( part instanceof Plugin )
343 {
344 Plugin plugin = (Plugin) part;
345 Class clazz = plugin.getPluginClass();
346 final String key = uri + ":" + name;
347 getProject().log( "installing single task plugin [" + key + "]", Project.MSG_VERBOSE );
348 super.addTaskDefinition( key, clazz );
349 }
350 else if( part instanceof Resource )
351 {
352 Resource res = (Resource) part;
353 String resource = res.getPath();
354 getProject().log( "installing antlib plugin [" + resource + "]", Project.MSG_VERBOSE );
355 ClassLoader classloader = part.getClassLoader();
356 InputStream input = classloader.getResourceAsStream( resource );
357 if( null == input )
358 {
359 final String error =
360 "Cannot load resource ["
361 + resource
362 + "] because it does not exist within the cloassloader defined by the uri ["
363 + uri
364 + "]"
365 + "\n" + classloader.toString();
366 throw new BuildException( error );
367 }
368
369 Element root = ElementHelper.getRootElement( input );
370 Element[] tasks = ElementHelper.getChildren( root, "taskdef" );
371 for( int i=0; i < tasks.length; i++ )
372 {
373 Element task = tasks[i];
374 String key = urn + ":" + ElementHelper.getAttribute( task, "name" );
375 getProject().log( "installing task [" + key + "]", Project.MSG_VERBOSE );
376 String classname = ElementHelper.getAttribute( task, "classname" );
377 Class clazz = classloader.loadClass( classname );
378 super.addTaskDefinition( key, clazz );
379 }
380 Element[] types = ElementHelper.getChildren( root, "typedef" );
381 for( int i=0; i < types.length; i++ )
382 {
383 Element type = types[i];
384 String key = urn + ":" + ElementHelper.getAttribute( type, "name" );
385 getProject().log( "installing type [" + key + "]", Project.MSG_VERBOSE );
386 String classname = ElementHelper.getAttribute( type, "classname" );
387 Class clazz = classloader.loadClass( classname );
388 super.addDataTypeDefinition( key, clazz );
389 }
390 }
391 m_URIS.add( uri );
392 }
393 catch( BuildException e )
394 {
395 throw e;
396 }
397 catch( Throwable e )
398 {
399 final String error =
400 "Could not load plugin: " + uri;
401 throw new BuildException( error, e );
402 }
403 }
404
405 /**
406 * Returns the current project.
407 * @return the project
408 */
409 private Project getProject()
410 {
411 return m_project;
412 }
413
414 /**
415 * Convert a urn to a url wrapping any errors in a build exception.
416 * @param urn the urn
417 * @return the uri
418 * @exception BuildException if a convertion error occurs
419 */
420 private URI convertToURI( String urn ) throws BuildException
421 {
422 try
423 {
424 return new URI( urn );
425 }
426 catch( Exception e )
427 {
428 final String error =
429 "Unable to convert the urn ["
430 + urn
431 + "] to a uri.";
432 throw new BuildException( error, e );
433 }
434 }
435
436 /**
437 * Notification that the build has started.
438 * @param event the build event
439 * @exception BuildException if a build error occurs
440 */
441 public void buildStarted( BuildEvent event )
442 throws BuildException
443 {
444 initialize( event.getProject(), false );
445 }
446
447 /**
448 * Notification that a sub build has started.
449 * @param event the build event
450 */
451 public void subBuildStarted( BuildEvent event )
452 {
453 initialize( event.getProject(), true );
454 }
455
456 /**
457 * Notification that a sub build has finished.
458 * @param event the build event
459 */
460 public void subBuildFinished( BuildEvent event )
461 {
462 }
463
464 /**
465 * Notification that the build has finished.
466 * @param event the build event
467 */
468 public void buildFinished( BuildEvent event )
469 {
470 }
471
472 /**
473 * Notification that the build target has started.
474 * @param event the build event
475 */
476 public void targetStarted( BuildEvent event )
477 {
478 }
479
480 /**
481 * Notification that the build target has finished.
482 * @param event the build event
483 */
484 public void targetFinished( BuildEvent event )
485 {
486 }
487
488 /**
489 * Notification that the build task has started.
490 * @param event the build event
491 */
492 public void taskStarted( BuildEvent event )
493 {
494 }
495
496 /**
497 * Notification that the build task has finaished.
498 * @param event the build event
499 */
500 public void taskFinished( BuildEvent event )
501 {
502 }
503
504 /**
505 * Notification of a message logged.
506 * @param event the build event
507 */
508 public void messageLogged( BuildEvent event )
509 {
510 }
511 }
512